package com.spring.service;

import java.io.File;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Index;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.SimpleFragmenter;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.wltea.analyzer.lucene.IKAnalyzer;
import org.wltea.analyzer.lucene.IKQueryParser;

import com.spring.annotation.search.Searchable;
import com.spring.annotation.search.SearchableComponent;
import com.spring.annotation.search.SearchableId;
import com.spring.annotation.search.SearchableProperty;
import com.spring.bean.page.Page;
import com.spring.bean.page.PageView;
import com.spring.exception.LuceneException;
import com.spring.utils.Constants;
import com.spring.utils.StringUtils;
import com.spring.utils.SystemProperties;

/**
 * 索引工具类
 * @author jhc
 */
public class LuceneSupport {
	
	// @param calcNum 计数器， 初始值为1 
	private static int calcNum = 1;
	// signNum 执行标志， 初始值为1 
	private static int signNum = 1;
	// 新建解析器
	private static Analyzer analyzer = new IKAnalyzer();
	
	/**
	 *  新建IndexWriter 
	 */
	private static <T> IndexWriter buildIndexWriter(T t) throws LuceneException {
		
		try {
			Directory directory = buildDirectory(t);
			IndexWriterConfig config = new IndexWriterConfig(
					SystemProperties.version, analyzer);
			// 建立写索引
			return new IndexWriter(directory, config);
		} catch (Exception e) {
			throw new LuceneException("LuceneSupport --- fail to create write-index : " + e.getMessage());
		}
	}
	
	/**
	 * 新建Directory
	 */
	@SuppressWarnings("unchecked")
	private static <T> Directory buildDirectory(T t) throws LuceneException {
		try {
			// 系统索引路径
			String indexPath = SystemProperties.indexPath;
			Class<T> cls = (Class<T>) t.getClass();
			// 是否有_searchable注解，并获取名字拼装索引路径
			if(cls.isAnnotationPresent(Searchable.class)){
				Searchable searchable = cls.getAnnotation(Searchable.class);
				String name = searchable.name();
				if(StringUtils.isBlank(name)){
					throw new LuceneException("LuceneSupport --- search entity name is null, please check the entity !");
				} 
				indexPath = indexPath + File.separator + name;
			} else {
				throw new LuceneException("LuceneSupport --- entity doesn't have @searchable , please check the entity !");
			}
			return FSDirectory.open(new File(indexPath));
		} catch (Exception e) {
			throw new LuceneException("LuceneSupport --- io exception : " + e.getMessage());
		}
	}
	
	/**
	 * 建立索引
	 * @param <T>
	 * @param t
	 * @param doNum
	 */
	public static <T> void createIndex(T t, int doNum) throws LuceneException {
		try {
			// 建立doc对象
			Document doc = new Document();
			IndexWriter writer = buildIndexWriter(t);
			// 建立索引
			doCreateIndex(t, doc, doNum, calcNum, signNum);
			// 将doc对象写入对应路径
			writer.addDocument(doc);
	        writer.close();
		} catch (Exception e) {
			throw new LuceneException("LuceneSupport --- io exception : " + e.getMessage());
		}
	}
	
	/**
	 * doCreateIndex
	 * @param <T>
	 * @param t
	 * @param doc
	 * @param doNum 迭代次数， 默认为1
	 * @param calcNum 计数器， 初始值为1
	 * @param signNum 执行标志， 初始值为1
	 * @throws LuceneException
	 */
	@SuppressWarnings("unchecked")
	private static <T> void doCreateIndex(T t, Document doc, int doNum, int calcNum, int signNum) throws LuceneException{
		try {
			// 判断是否有id字段
			boolean hasSearchableId = false;
			// 取得属性名称
			Field[] fields = t.getClass().getDeclaredFields(); 
			for(Field f : fields){
				// @SearchableId，不分词，建立索引，已做删改的标志, 且只在第一次循环时判断
				if(f.isAnnotationPresent(SearchableId.class) && signNum == 1){
					hasSearchableId = true;
					// 字段名称，大多为id
					String docName = f.getName();
					// 获取id的值
					Object val = PropertyUtils.getProperty(t, f.getName());
					if(val == null){
						throw new LuceneException("LuceneSupport --- SearchableId value is null, can't build index!");
					}
					String valStr = val.toString();
					doc.add(new org.apache.lucene.document.Field(docName, valStr, Store.YES, Index.NOT_ANALYZED));
					/* 搞个事务控制*/
					continue;
				} 
				// @SearchableProperty
				if(f.isAnnotationPresent(SearchableProperty.class)){
					SearchableProperty property = f.getAnnotation(SearchableProperty.class);
					String docName = property.name();
					if(StringUtils.isBlank(docName)){
						docName = f.getName();
					}
					Object val = PropertyUtils.getProperty(t, f.getName());
					String valStr = "";
					if(val != null){
						valStr = val.toString();
					}
					doc.add(new org.apache.lucene.document.Field(docName, valStr, property.store(), property.index()));
					continue;
				} 
				// @SearchableComponent关联的外部属性
				if(f.isAnnotationPresent(SearchableComponent.class) && doNum >= calcNum){
					calcNum++;
					T subEntity = (T) PropertyUtils.getProperty(t, f.getName());
					if(null == subEntity)
						continue;
					doCreateIndex(subEntity, doc, doNum, calcNum, (signNum+1));
				}
			}
			if(!hasSearchableId && signNum == 1){
				throw new LuceneException("LuceneSupport --- searchableId is not exsit, please check your entity's annotation!");
			}
		} catch (Exception e) {
			throw new LuceneException("LuceneSupport ---  doCreateIndex : " + e.getMessage());
		} 
	}
	
	/**
	 * 通过documentId删除索引
	 * @param <T>
	 */
	public static <T> void rebuildIndexById(T t, int doNum) throws LuceneException{
		try {
			// 建立doc对象
			Document doc = new Document();
			IndexWriter writer = buildIndexWriter(t);
			// 取得属性名称
			Field[] fields = t.getClass().getDeclaredFields(); 
			for(Field f : fields){
				if(f.isAnnotationPresent(SearchableId.class)){
					String name = f.getName();
					Object val = BeanUtils.getProperty(t, name);
					if(val == null){
						throw new LuceneException("LuceneSupport --- SearchableId value is null, can't delete index by SearchableId ");
					}
					String valStr = val.toString();
					writer.deleteDocuments(new Term(name, valStr));
					break;
				}
			}
			doCreateIndex(t, doc, doNum, doNum, doNum);
			writer.addDocument(doc);
			writer.close();
		} catch(Exception e) {
			throw new LuceneException("LuceneSupport --- io exception : " + e.getMessage());
		}
	}
	
	@SuppressWarnings("deprecation")
	// 索引的分页查询
	public static <T> PageView<T> searchForPage(Class<T> cls, String keyword, List<String> columns, Page page, int doNum) throws LuceneException{
		try {
			int clacNum = 1;
			List<T> list = new ArrayList<T>();
			T t = cls.newInstance();
			Directory directory = buildDirectory(t);
			IndexSearcher isearcher = new IndexSearcher(directory);
			TopScoreDocCollector topCollector = null;
			// 要被查询的字段名称数组
			String[] columnStrs = columns.toArray(new String[columns.size()]);
			Query query = IKQueryParser.parseMultiField(columnStrs, keyword);
			//得到查询结果的总记录数  
			topCollector = TopScoreDocCollector.create(isearcher.maxDoc(), false);  
			isearcher.search(query, topCollector);  
			ScoreDoc[] scoreDocs = topCollector.topDocs(page.getFirstCodeIndex(), Constants.MAX_RESULT).scoreDocs;  
			
			// 高亮显示设置
			Highlighter highlighter = null;
			SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter(
			    "<font color='red'>", "</font>");
			highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
			// 这个100是指定关键字字符串的context的长度，你可以自己设定，因为不可能返回整篇正文内容
			highlighter.setTextFragmenter(new SimpleFragmenter(100));

			
			for(ScoreDoc scoreDoc : scoreDocs){
				 t = DocToEntity(scoreDoc, isearcher, highlighter, cls, doNum, clacNum);
				 list.add(t);
			}
			PageView<T> pageView = new PageView<T>(list, page.getCurrentpage());
			pageView.setTotalNum(isearcher.maxDoc());
			return pageView;
		} catch (Exception e) {
			throw new LuceneException("LuceneSupport --- searchForPage : " + e.getMessage());
		} 
	}
	
	@SuppressWarnings({ "rawtypes", "unchecked" })
	// 将查询出的结果转换为对象类型
	private static <T> T DocToEntity(ScoreDoc scoreDoc, IndexSearcher isearcher, Highlighter highlighter, Class<T> cls, int doNum, int calcNum) throws LuceneException {
		try {
			T t = cls.newInstance();	
			Document document = isearcher.doc(scoreDoc.doc);
				Field[] fields = cls.getDeclaredFields();
				for(Field f : fields){
					if(f.isAnnotationPresent(SearchableId.class)){
						SearchableId searchableId = f.getAnnotation(SearchableId.class);
						String idName = searchableId.name();
						String columName = f.getName();
						if(StringUtils.isBlank(idName)){
							idName = columName;
						}
						BeanUtils.setProperty(t, columName, document.get(idName));
						continue;
					} 
					// @SearchableProperty
					if(f.isAnnotationPresent(SearchableProperty.class)){
						SearchableProperty property = f.getAnnotation(SearchableProperty.class);
						String str = "";
						String docName = property.name();
						String columName = f.getName();
						if(StringUtils.isBlank(docName)){
							docName = columName;
						}
						str = document.get(docName);
						if(property.index() == Index.ANALYZED){
							TokenStream tokenStream = analyzer.tokenStream("",new StringReader(str));  
				            str = highlighter.getBestFragment(tokenStream, str);  
						}
						BeanUtils.setProperty(t, columName, str);
						continue;
					} 
					// @SearchableComponent关联的外部属性
					if(f.isAnnotationPresent(SearchableComponent.class) && doNum >= calcNum){
						calcNum++;
						SearchableComponent searchableComponent = f.getAnnotation(SearchableComponent.class);
						String className = searchableComponent.name();
						String columName = f.getName();
						if(StringUtils.isBlank(className)){
							throw new LuceneException("LuceneSupport --- SearchableComponent className is null, please check bean");
						}
						Class subCls = Class.forName(className);
						T subEntity = (T) subCls.newInstance();					
						subEntity = (T) DocToEntity(scoreDoc, isearcher, highlighter, subCls, doNum, calcNum);
						BeanUtils.setProperty(t, columName, subEntity);
					}
				}
			return t;
		} catch (Exception e) {
			throw new LuceneException("LuceneSupport --- DocToEntity : " + e.getMessage());
		} 
	}
}
